import streamlit as st
from streamlit_authenticator.authenticate.cookie import CookieHandler
from streamlit_authenticator.utilities.hasher import Hasher
from datetime import date, timedelta
from config import settings
from database.queries.user_orm import SynOrmUserQueries
from database.queries.physical_activity_orm import SynOrmPhysicalActivityQueries
from database.queries.purpose_orm import SynOrmPurposeQueries
from database.models.base import Sex
from time import sleep


""" 
Only one object at a time!!! 
Please don't create more than one authenticator in your application. 

Lyrics:
I tried implementing it as a singleton and it might worked, 
but it's important to note that Python objects can live beyond 
the session_state period. For example, after a streamlight rerun, 
the object still exists (I think), but the session_state keys 
defined in the __init__ method are removed. I believe this is what caused 
all the errors I encountered when trying to implement this feature. 

Perhaps I'll revisit this later.
"""
class Authenticator():
    def __init__(self):
        # Binds with CookieHandler
        if "logout" not in st.session_state:
            st.session_state["logout"] = False
        if "username" not in st.session_state:
            st.session_state["username"] = None
        # My own key which describes is user authorized
        if "authorized" not in st.session_state:
            st.session_state["authorized"] = False
        
        self.__cookie_handler = CookieHandler(
            settings.COOKIE_AUTH_NAME, 
            settings.COOKIE_AUTH_KEY,
        )
        
        if cookie := self.__cookie_handler.get_cookie():
            if user := SynOrmUserQueries.get_user_by_username(
                cookie["username"],
            ):
                st.session_state["username"] = user.username
                st.session_state["authorized"] = True
        # cookie_handler behave like a very lazy brrrruh. 
        # He wakes up only the second time get_cookie(), 
        # which causes rerun all page immediately.
        # So, we sleep 1 second to wait his awakening.
        # It took very long time to understand that... 
        # But, apparently, his creator use him EXACTLY the same :)
        sleep(1)
        
        
    def is_authorized(self) -> bool:
        if "authorized" in st.session_state:
            return st.session_state["authorized"]
        return False
        
        
    def authorization_form(self) -> bool:
        if "auth_mode" not in st.session_state:
            st.session_state["auth_mode"] = "login"
        
        if not self.is_authorized():
            if st.button(
                label="Смена входа и регистрации", 
                use_container_width=True,
                key="auth_change",
            ):
                mode = st.session_state["auth_mode"]
                st.session_state["auth_mode"] = \
                    "login" if mode == "signup" else "signup"

            if st.session_state["auth_mode"] == "login":
                self.__login_form()
            else:
                self.__signup_form()

        return self.is_authorized()
            
            
    def __login_form(self):
        if "login_bad_auth" not in st.session_state:
            st.session_state["login_bad_auth"] = False
        
        with st.form(key="login"):
            if st.session_state["login_bad_auth"]:
                st.error("Неверный логин или пароль")
                
            st.header("Авторизация")
            st.text_input("Логин", key="login_username")
            st.text_input("Пароль", key="login_password", type="password")
            st.form_submit_button(
                label="Войти", 
                type="primary", 
                on_click=self.__try_login,
            )
        
        
    def __try_login(self):
        if self.__validate_login():
            st.session_state["username"] = st.session_state["login_username"]
            st.session_state["authorized"] = True
            st.session_state["login_bad_auth"] = False
            self.__cookie_handler.set_cookie()
        else:
            st.session_state["login_bad_auth"] = True
        
        
    def __validate_login(self) -> bool:
        entered_username = st.session_state["login_username"]
        entered_password = st.session_state["login_password"]
        if user_pass_hash := SynOrmUserQueries.get_user_password_hash(
            entered_username,
        ):
            return Hasher.check_pw(entered_password, user_pass_hash)
        return False
        
        
    def __signup_form(self):
        if "signup_bad_username_unique" not in st.session_state:
            st.session_state["signup_bad_username_unique"] = False
        if "signup_bad_password_length" not in st.session_state:
            st.session_state["signup_bad_password_length"] = False
        if "signup_bad_password_match" not in st.session_state:
            st.session_state["signup_bad_password_match"] = False
        
        with st.form(key="signup"):
            if st.session_state["signup_bad_username_unique"]:
                st.error("Пользователь с таким именем уже существует")
            if st.session_state["signup_bad_password_length"]:
                st.error("Пароль должен быть не менее 8 символов")
            if st.session_state["signup_bad_password_match"]:
                st.error("Пароль не совпадает")
                
            st.header("Регистрация")
            st.text_input("Имя", placeholder="Имя", key="signup_name")
            st.text_input("Логин", placeholder="Логин", key="signup_username")
            st.text_input(
                label="Пароль (>= 8 символов)", 
                placeholder="Пароль",
                type="password", 
                key="signup_password",
            )
            st.text_input(
                label="Повторите пароль", 
                placeholder="Повторите пароль",
                type="password", 
                key="signup_password_match",
            )
            st.date_input(
                label="Дата рождения", 
                format="DD.MM.YYYY",
                min_value=date(1900, 1, 1), 
                max_value=(date.today()-timedelta(days=18*365.25+1)), # only 18+ users
                key="signup_birth",
            ) 
            st.number_input(
                label="Вес (кг)", 
                placeholder="Вес", 
                min_value=20, 
                key="signup_weight",
            )
            st.number_input(
                label="Рост (см)", 
                placeholder="Рост", 
                min_value=100, 
                key="signup_height",
            )
            st.selectbox("Пол", options=[e.name for e in Sex], key="signup_sex")
            st.selectbox(
                label="Цель", 
                options=SynOrmPurposeQueries.get_purposes_names(),
                key="signup_purpose",
            )
            st.selectbox(
                label="Физическая активность", 
                options=SynOrmPhysicalActivityQueries.get_physical_activities_names(),
                key="signup_physical_activity",
            )
            st.form_submit_button(
                label="Зарегистрироваться", 
                type="primary", 
                on_click=self.__try_signup,
            )
        
        
    def __try_signup(self):
        if self.__validate_signup():
            if user := SynOrmUserQueries.signup_user(
                name=st.session_state["signup_name"],
                username=st.session_state["signup_username"],
                password_hash=Hasher([st.session_state["signup_password"]]).generate()[0],
                birth=st.session_state["signup_birth"],
                weight=st.session_state["signup_weight"],
                height=st.session_state["signup_height"],
                sex=st.session_state["signup_sex"],
                purpose_name=st.session_state["signup_purpose"],
                physical_activity_name=st.session_state["signup_physical_activity"],
            ):
                st.session_state["username"] = user.username
                st.session_state["authorized"] = True
                st.session_state["login_bad_auth"] = False
                self.__cookie_handler.set_cookie()
        
        
    def __validate_signup(self) -> bool:
        entered_username = st.session_state["signup_username"]
        entered_password = st.session_state["signup_password"]
        entered_password_match = st.session_state["signup_password_match"]
        
        bad_un = st.session_state["signup_bad_username_unique"] = \
            not SynOrmUserQueries.check_username_unique(entered_username)
        bad_pl = st.session_state["signup_bad_password_length"] = \
            len(entered_password) < 8 
        bad_pm = st.session_state["signup_bad_password_match"] = \
            entered_password != entered_password_match
            
        return not (bad_un or bad_pl or bad_pm)
            
            
    def logout_button(self):
        if st.button("Выйти", use_container_width=True):
            self.__cookie_handler.delete_cookie()
            st.session_state["authorized"] = False
            st.session_state["username"] = None
            st.session_state["logout"] = True
            # wait for ensure that cookie is deleted
            sleep(1)
            st.rerun()
